/* output.c */

#include <stdlib.h>

#include "btree.h"
#include <stdio.h>
#include <string.h>
#include "output.h"
#include "global_macros.h"
#include "input.h"

#define WRITE_MODE "w+b"
#define EMPTY_CODE ""

#define CHECK_WRITING_CHAR(VAR, FUNCTION) \
{if (VAR == EOF) { printf(FUNCTION ": error while writing file\n"); \
                    exit(EXIT_FAILURE); }}

/** The compressed result is written into this file. */
FILE *output_file;

/**
 * Write the code table into the output file header.
 * @param codes - Array containing the codes. The index of a code is equal to
 *                numeric value of the char.
 */
static void write_code_table(char *codes[]);

/**
 * Writes one code from the code table to the output file.
 * @param symbol - The numeric value of the symbol for which the code is
 *                 written.
 * @param code - The string representation of the code.
 */
static void write_code(int symbol, char *code);

/**
 * Writes the original count of symbols of the uncompressed file into the
 * output file header.
 * @param count - The count of symbols in the uncompressed file.
 */
static void write_symbol_count(int count);

/**
 * Write the source text into the output file and replace each symbol with
 * its code from the code table.
 * @param source source text
 */
static void write_source(char *source, char *codes[]);

/**
 * Write the source text into the output file and replace each code with
 * its symbol from the code tree.
 * @param source source text
 */
static void write_source_2(unsigned char *source, BTREE *code_tree, int symbol_count);

extern void write_compressed_file(char *out_filename, char *source,
                                  char *codes[])
{
    output_file = fopen(out_filename, WRITE_MODE);
    CHECK_OPEN_FILE(output_file, "write_compressed_file");

    printf("write symbol count\n");
    write_symbol_count(strlen(source) - 1);
    printf("write code table\n");
    write_code_table(codes);
    printf("write source\n");
    write_source(source, codes);

    fclose(output_file);
}

static void write_symbol_count(int count)
{
    int i;
    int check = fprintf(output_file, "%i", count);
    CHECK_WRITING_CHAR(check, "write_symbol_count");

    for (i = 0; i < 1; i++)
    {
        check = fputc(TABLE_END, output_file);
        CHECK_WRITING_CHAR(check, "write_symbol_count");
    }

}

static void write_code_table(char *codes[])
{
    int i;
    int check;

    for (i = 0; i < ANSI_COUNT; i++)
    {
        /* check if code exists */
        if (codes[i] != 0)
        {
            write_code(i, codes[i]);
        }
    }

    /* write end of code table */
    for (i = 0; i < 1; i++)
    {
        check = fputc(TABLE_END, output_file);
        CHECK_WRITING_CHAR(check, "write_code");
    }
}

static void write_code(int symbol, char *code)
{
    int i;
    int check;

    /* write symbol */
    check = fputc(symbol, output_file);
    CHECK_WRITING_CHAR(check, "write_code");

    /* write code */
    for (i = 0; i < strlen(code); i++)
    {
        check = fputc(code[i], output_file);
        CHECK_WRITING_CHAR(check, "write_code");
    }

    /* write delims */
    for (i = 0; i < 1; i++)
    {
        check = fputc(CODE_DELIM, output_file);
        CHECK_WRITING_CHAR(check, "write_code");
    }
}

static void write_source(char *source, char *codes[])
{
    int i;
    int j;

    /* for checking the write success */
    int check;

    /* the compressed output is written into this char bit for bit */
    unsigned char write_code = 0;

    /* indicates the number of bits that are set in the current code char */
    int bits_set = 0;

    /* the currently encoded symbol */
    unsigned char symbol;

    /* the string representation of the bit code of the encoded symbol */
    char *code;

    for (i = 0; source[i] != EOF; i++)
    {
        symbol = source[i];
        code = codes[symbol];
        /*
                printf("%i", symbol);
                printf("%c\n", symbol);
         */
        /* write code to char */
        for (j = 0; j < strlen(code); j++)
        {
            if (code[j] == '1')
            {
                write_code |= 1;
            }

            bits_set++;
            

            if (bits_set == CHAR_SIZE)
            {
                /* all bits set, flush the char */
                check = fputc(write_code, output_file);
                CHECK_WRITING_CHAR(check, "write_source");
                write_code = 0;
                bits_set = 0;
            } else {
              write_code <<= 1;
            }
        }
    }

    /* fill up the remaining bits with 0 */
    if (bits_set != 0)
    {
        /* one 0 is already set */
        bits_set++;
        /* now shift the rest */
        write_code <<= CHAR_SIZE - (bits_set);
        check = fputc(write_code, output_file);
        CHECK_WRITING_CHAR(check, "write_source");
    }


}

extern void write_uncompressed_file(char *out_filename, unsigned char *source,
                                    BTREE *code_tree, int symbol_count)
{
    output_file = fopen(out_filename, WRITE_MODE);
    CHECK_OPEN_FILE(output_file, "write_uncompressed_file");

    printf("write source\n");

    write_source_2(source, code_tree, symbol_count);

    fclose(output_file);
}

static void write_source_2(unsigned char *source, BTREE *code_tree, int symbol_count)
{
    int i;

    int j;

    BTREE_NODE *current_node;

    unsigned char byte;

    int digit;

    /* for checking the write success */
    int check;

    int symbols_read;

    current_node = btree_get_root(code_tree);

    symbols_read = 0;

    for (i = 0; symbols_read < symbol_count; i++)
    {
        byte = source[i];
        for (j = 0; j < 8 && symbols_read < symbol_count; ++j)
        {
            digit = byte & (0x1 << (7 - j));
            if (digit > 0)
            {
                current_node = btreenode_get_left(current_node);
            }
            else if (digit == 0)
            {
                current_node = btreenode_get_right(current_node);
            }
            if (!current_node)
            {
                printf("Unknown code sequence encountered.");
                exit(EXIT_FAILURE);
            }
            if (btreenode_is_leaf(current_node))
            {
                check = fputc(btreenode_get_data(current_node), output_file);
                CHECK_WRITING_CHAR(check, "write_source_2");
                current_node = btree_get_root(code_tree);
                symbols_read++;
            }
        }

    }
}
